iT邦幫忙

2024 iThome 鐵人賽

DAY 12
1
JavaScript

Signal API in Angular系列 第 12

Day 12 - 介紹 Signal Input

  • 分享至 

  • xImage
  •  

Angular架構使開發人員能夠將資料從父組件傳遞到子組件。子組件由可在HTML範本中顯示或進一步處理以產生新的結果。 Angular在signal之前使用了@Input,但Angular 17引入了可以從父組件輸入signal inputs

Signal input可以是必需的 (required),也可以是可選的 (optional)。 必需的signal input可以選擇傳入選項物件。可選signal input輸入初始值,也可以選擇傳入選項物件。

選項物件包含兩個屬性

  • alias - signal input的別名
  • transform - 將輸入值轉換為最終值的函數

必需的 Signal Input

import { Component, input } from '@angular/core';

@Component({
 selector: 'app-star-war',
 standalone: true,
 template: `<p>Jedi Id: {{ jedi() }}</p>`,
})
export class StarWarComponent {
 // required signal input
 jedi = input.required<number>();
}

建立一個具有多個signal inputs的StarWarComponentsignal input與App組件綁定。 該組件具有HTML範本顯示必需的訊號輸入, jedi

import { Component, VERSION } from '@angular/core';
import { StarWarComponent } from './star-war.component';

@Component({
 selector: 'app-root',
 standalone: true,
 imports: [StarWarComponent],
 template: `
   <app-star-war [jedi]="1" starShip="Star Destroyer" rgbTuple="yellow" />
   <app-star-war [jedi]="10" />`,
})
export class App {}

App組件有兩個StarWarComponent。 第一個StarWarComponentjedi輸入是1,第二個StarWarComponentjedi輸入是10。

可選的 Signal Input

import { Component, input } from '@angular/core';

@Component({
 selector: 'app-star-war',
 standalone: true,
 template: `
  <p>Jedi Id: {{ jedi() }}</p>
  <p>Sith Id: {{ sith() }}</p>
 `,
})
export class StarWarComponent {
 // required signal input
 jedi = input.required<number>();

 // optional signal input with a value
 sith = input(4);
}

sith 是可選的signal input,初始值為4。當未向組件提供sith輸入值時,將使用初始值。

import { Component } from '@angular/core';
import { StarWarComponent } from './star-war.component';

@Component({
 selector: 'app-root',
 standalone: true,
 imports: [StarWarComponent],
 template: `
   <app-star-war [jedi]="1" />
   <app-star-war [jedi]="10" [sith]="44" />
 `,
})
export class App {}

第一個StarWarComponent 沒有輸入sith的值;因此,sith為 4。第二個StarWarComponentsith為 44。

帶有別名的 Signal Input

import { NgStyle } from '@angular/common';
import { HttpClient } from '@angular/common/http';
import { ChangeDetectionStrategy, Component, computed, effect, inject, Injector, input, signal } from '@angular/core';
import { getPerson, Person } from './star-war.api';
import { StarWarPersonComponent } from './star-war-person.component';
import { RgbType, validateRGB } from './rgb';
import { toObservable, toSignal } from '@angular/core/rxjs-interop';
import { switchMap } from 'rxjs';

@Component({
 selector: 'app-star-war',
 standalone: true,
 imports: [NgStyle, StarWarPersonComponent],
 template: `
   <h3>Star War Jedi vs Sith</h3>
   <div [ngStyle]="backgrounStyle()">
     <div style="display: flex; justify-content: space-between;">
       <p>Jedi Id: {{ jedi() }}</p>
       <p>Sith Id: {{ sith() }}</p>
       <p>Star ship: {{ ship() }}</p>
     </div>
     <app-star-war-person [person]="fighter()" kind="Jedi Fighter" />
     <app-star-war-person [person]="sithLord()" kind="Sith Lord" />
   </div>
 `,
})
export class StarWarComponent {
 // required signal input
 jedi = input.required<number>();

 // optional signal input with a value
 sith = input(4);

 // signal input with alias
 ship = input('Star Destroyer', { alias: 'starShip' })

 // signal input with transform
 rgbs = input.required({
   alias: 'rgbTuple',
   transform: (v: RgbType) => validateRGB(v)
 });
}

ship signal input的別名是 starShip。 Signal 的初始值為"Star Destroyer"。rgbs signal input的別名是 rgbTuple

import { Component } from '@angular/core';
import { StarWarComponent } from './star-war.component';

@Component({
 selector: 'app-root',
 standalone: true,
 imports: [StarWarComponent],
 template: `
   <app-star-war [jedi]="1" rgbTuple="yellow" />
   <app-star-war [jedi]="10" [sith]="44" starShip="Jedi starfighter" [rgbTuple]="lightBlue" />`,
})
export class App {
 lightBlue = [137, 207, 240] as [number, number, number];
}

在第二個 StarWarComponent 中,輸入名稱分別為 starShiprgbTuple

具有變換功能的 Signal Input

export type RgbType = [number, number, number] | string;

export function validateRGB(rgbValues: RgbType): string {
 if (typeof rgbValues === 'string') {
   return rgbValues;
 }

 const minRGB = 0;
 const maxRGB = 255;
 const [r, g, b] = rgbValues.map(value => Math.max(minRGB, Math.min(value, maxRGB)));

 return `rgb(${r}, ${g}, ${b})`;
}

validateRGB 是一個接受字串或元組並傳回 RGB 值的函數。

import { Component, input } from '@angular/core';
import { RgbType, validateRGB } from './rgb';

@Component({
 selector: 'app-star-war',
 standalone: true,
 template: '...',
})
export class StarWarComponent {
 // signal input with transform
 rgbs = input.required({
   alias: 'rgbTuple',
   transform: (v: RgbType) => validateRGB(v)
 });
}

變換函數將輸入 (input) 轉換為 RGB 代碼 (RGB code)。

import { Component } from '@angular/core';
import { StarWarComponent } from './star-war.component';

@Component({
 selector: 'app-root',
 standalone: true,
 imports: [StarWarComponent],
 template: `
   <app-star-war [jedi]="1" rgbTuple="yellow" />
   <app-star-war [jedi]="10" [sith]="44" starShip="Jedi starfighter" [rgbTuple]="lightBlue" />
 `,
})
export class App {
 lightBlue = [137, 207, 240] as [number, number, number];
}

lightBlue 值轉換為 rgb(137, 207, 240); 黃色並按原樣傳回。

從 Signal Input 導出 Computed Signal

我們可以根據 signal input 建立 computed signal

import { NgStyle } from '@angular/common';
import { Component, computed, input } from '@angular/core';
import { RgbType, validateRGB } from './rgb';

@Component({
 selector: 'app-star-war',
 standalone: true,
 imports: [NgStyle],
 template: `<div [ngStyle]="backgrounStyle()"></div>`,
})
export class StarWarComponent {
 // signal input with transform
 rgbs = input.required({
   alias: 'rgbTuple',
   transform: (v: RgbType) => validateRGB(v)
 });

 // computed signal based on signal input
 backgrounStyle = computed(() => ({
   backgroundColor: this.rgbs()
 }));
}

backgroundStyle 傳回背景顏色的 CSS 樣式。 CSS 樣式的背景顏色值等於rgbs signal input。然後,backgroundStyle signal的值被分配到NgStyle

Use signal input with toObservable

import { Component, inject, Injector, input, signal } from '@angular/core';
import { getPerson, Person } from './star-war.api';
import { toObservable, toSignal } from '@angular/core/rxjs-interop';
import { switchMap } from 'rxjs';

@Component({
 selector: 'app-star-war',
 standalone: true,
 imports: [StarWarPersonComponent],
 template: `
     <p>Sith Id: {{ sith() }}</p>
     <app-star-war-person [person]="sithLord()" kind="Sith Lord" />`,
})
export class StarWarComponent {
 // optional signal input with a value
 sith = input(4);

 injector = inject(Injector);
 sithLord = toSignal(toObservable(this.sith).pipe(switchMap((v) => getPerson(v, this.injector))));
}

類似地,StarWarComponent 也可以使用 toObservablesignal input轉換為 Observable。 在範例中,該組件將 sith signal input轉換為 Observable,並將 id 傳送到函數以檢索星際大戰人物。最後,toSignal 函數會取得 Observable,建立一個signal,並將其指派給 sithLord

@Component({
 selector: 'app-star-war',
 standalone: true,
 imports: [StarWarPersonComponent],
 template: `
     <app-star-war-person [person]="sithLord()" kind="Sith Lord" />
 `,
})
export class StarWarComponent {}

在範本中,sithLordStarWarPersonComponent 的輸入 (input),用於顯示人物的詳細資料。

import {Component, input } from '@angular/core';
import { Person } from './star-war.api';

@Component({
 selector: 'app-star-war-person',
 standalone: true,
 template: `
     @let p = person();
     @if(p) {
       <p>Name: {{ p.name }}</p>
       <p>Height: {{ p.height }}</p>
     } @else {
       <p>No info</p>
     }
   </div>`,
})
export class StarWarPersonComponent {
 // required signal input
 person = input<Person | undefined>(undefined);
}

StarWarPersonComponent 有一個必需的person signal input來顯示那個人的詳細資料。

在effect使用signal input

類似地,effect可以追蹤signal input執行程式碼。 StarWarComponenteffect中使用jedi signal input,呼叫 Star War API來檢索人物,並設定fighter signal。訂閱 Observable 後,OnCleanUp 清理函數會在銷毀effect前取消subscription

import { Component, effect, inject, Injector, input, signal } from '@angular/core';
import { getPerson, Person } from './star-war.api';
import { StarWarPersonComponent } from './star-war-person.component';

@Component({
 selector: 'app-star-war',
 standalone: true,
 imports: [StarWarPersonComponent],
 template: `
     <p>Jedi Id: {{ jedi() }}</p> 
     <app-star-war-person [person]="fighter()" kind="Jedi Fighter" />`,
})
export class StarWarComponent {
 // required signal input
 jedi = input.required<number>();

 injector = inject(Injector);
 fighter = signal<Person | undefined>(undefined);
 
 constructor() {
   effect((OnCleanup) => {
     const sub = getPerson(this.jedi(), this.injector)
       .subscribe((result) => this.fighter.set(result));

     OnCleanup(() => sub.unsubscribe());
   });
 }
}

在範本中,fighterStarWarPersonComponent的輸入,用於顯示人物的詳細資料。

結論:

  • signal inputs是保存signal的輸入。
  • signal inputs可以是必需的 (required) 或可選的 (optional)。
  • 我們可以給signal inputs一個別名 (alias) 或一個變換函數 (transformation function來轉換輸入值。
  • computed signal可以從signal input得出新值。
  • toObservable 可以從signal input建立一個新的Observable
  • effect可以追蹤signal input執行程式碼。

鐵人賽的第12天就這樣結束了。

參考:


上一篇
Day 11 - explicitEffect - useEffect hook 的 Angular 版本
下一篇
Day 13 - 將路由資料綁定到 Signal Input
系列文
Signal API in Angular39
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言